
unit input_handler;

{$mode objfpc}{$H+}{$J-}
interface

uses FGL, SDL2, vector2, SysUtils, glog, Classes;

type
  TMouseButton = (LEFT = 0, MIDDLE = 1, RIGHT = 2);
  TJoystickList = specialize TFPGList<PSDL_Joystick>;



  TJoystickAxisList = specialize TFPGList<TVectorTuple2>;
  TBooleanList = specialize  TFPGList<boolean>;
  TJoystickButtonList = specialize TFPGList<TBooleanList>;


  TInputHandle = class
  public
    isQuit: boolean;
    destructor Destroy(); override;
    procedure Update;
    procedure InitJoysticks;
    procedure Reset;
    function IsInitialized: boolean;
    function xValue(deviceId, axisId: integer): integer;
    function yValue(deviceId, axisId: integer): integer;
    function IsButtonDown(deviceId, buttonId: integer): boolean;
    function IsMouseButtonDown(buttonId: integer): boolean;
    function IsKeyDown(scancode: TSDL_Scancode): boolean;
    function GetMousePosition(): TVector2;
    function IsActionPressed(action: string): boolean;
    function IsActionReleased(action: string): boolean;
    //function GetMouseButtonState():boolean;
    class function Instance: TInputHandle;

  private
    joystickDeadZone: integer;
    joystickList: TJoystickList;
    joystickAxisList: TJoystickAxisList;
    joystickButtonList: TJoystickButtonList;
    mousePosition: TVector2;
    mouseButtonList: TBooleanList;
    keyState: PUInt8;
    initialized: boolean;

    constructor Create;
    procedure OnJoystickAxisMotionEvent(deviceId, axisId, axisValue: integer);
    procedure OnJoystickButtonEvent(deviceId, buttonId: integer; isDown: boolean);
    procedure OnMouseMotionEvent(x, y: integer);
    procedure OnMouseButtonEvent(sdlButtonId: integer; isDown: boolean);
    procedure OnKeyEvent();

  end;



implementation

var
  m: TInputHandle;

function TInputHandle.IsActionPressed(action: string): boolean;
begin
  // TODO:
  Result := False;
end;

function TInputHandle.IsActionReleased(action: string): boolean;
begin
  // TODO:
  Result := False;
end;

function TInputHandle.IsInitialized: boolean;
begin
  Result := initialized;
end;

class function TInputHandle.Instance: TInputHandle;
begin
  if m = nil then
    m := TInputHandle.Create;
  Result := m;
end;

destructor TInputHandle.Destroy;
begin
  Reset;
  mouseButtonList.Free;
  joystickList.Free;
  joystickAxisList.Free;
  joystickButtonList.Free;
end;

procedure TInputHandle.Reset;
var
  i: integer;
begin
  for i := 0 to joystickList.Count - 1 do
  begin
    SDL_JoystickClose(joystickList[i]);
  end;
  joystickList.Clear;
  joystickAxisList.Clear;
  mouseButtonList.Clear;
  for i := 0 to joystickButtonList.Count - 1 do
  begin
    joystickButtonList[i].Clear;
    joystickButtonList[i].Free;
  end;
  joystickButtonList.Clear;
end;

constructor TInputHandle.Create;
var
  i: integer;
begin
  inherited Create;
  joystickDeadZone := 1000;

  mouseButtonList := TBooleanList.Create;

  for i := 0 to 2 do
  begin
    mouseButtonList.Add(False);
  end;

  joystickList := TJoystickList.Create;
  joystickAxisList := TJoystickAxisList.Create;
  joystickButtonList := TJoystickButtonList.Create;


end;

procedure TInputHandle.OnJoystickAxisMotionEvent(deviceId, axisId, axisValue: integer);
var
  r: integer;
  k: TVectorTuple2;
begin
  if joystickDeadZone < axisValue then
    r := 1
  else
  if axisValue < -joystickDeadZone then
    r := -1
  else
    r := 0;

  k := joystickAxisList[deviceId];

  case axisId mod 4 of
    0: k.x1 := r;
    1: k.y1 := r;

    2: k.x2 := r;
    3: k.y2 := r;
  end;

  joystickAxisList[deviceId] := k;
end;

procedure TInputHandle.OnJoystickButtonEvent(deviceId, buttonId: integer;
  isDown: boolean);
begin
  joystickButtonList[deviceId][buttonId] := isDown;
end;

procedure TInputHandle.onMouseMotionEvent(x, y: integer);
begin
  mousePosition.x := x;
  mousePosition.y := y;
end;

procedure TInputHandle.onMouseButtonEvent(sdlButtonId: integer; isDown: boolean);
var
  buttonId: TMouseButton;
begin
  case sdlButtonId of
    SDL_BUTTON_LEFT: buttonId := LEFT;
    SDL_BUTTON_MIDDLE: buttonId := MIDDLE;
    SDL_BUTTON_RIGHT: buttonId := RIGHT;
  end;

  mouseButtonList[Ord(buttonId)] := isDown;
end;

procedure TInputHandle.onKeyEvent();
begin
  keyState := SDL_GetKeyboardState(nil);
  (*
  if isKeyDown(SDL_SCANCODE_ESCAPE) then
    isQuit := True;
    *)
end;

procedure TInputHandle.InitJoysticks;
var
  i: integer;
  j: PSDL_Joystick;
  t: TVectorTuple2;
  k: integer;
  btnSet: TBooleanList;
begin
  if SDL_WasInit(SDL_INIT_JOYSTICK) = 0 then
    SDL_InitSubSystem(SDL_INIT_JOYSTICK);

  t.x1 := 0;
  t.x2 := 0;
  t.y1 := 0;
  t.y2 := 0;

  if SDL_NumJoysticks() > 0 then
  begin
    for i := 0 to SDL_NumJoysticks() do
    begin
      j := SDL_JoystickOpen(i);

      if j <> nil then
      begin
        joystickList.Add(j);
        joystickAxisList.Add(t);
      end;

      btnSet := TBooleanList.Create;
      for k := 0 to SDL_JoystickNumButtons(j) - 1 do
        btnSet.Add(False);
      joystickButtonList.Add(btnSet);

    end;
    SDL_JoystickEventState(1);    // SDL_EANBLE
  end;
  initialized := joystickList.Count > 0;

end;

function TInputHandle.xValue(deviceId, axisId: integer): integer;
begin
  Result := 0;

  if deviceId < joystickList.Count then
  begin
    case axisId of
      0: Result := round(joystickAxisList[deviceId].x1);
      1: Result := round(joystickAxisList[deviceId].x2);

    end;
  end;

end;

function TInputHandle.yValue(deviceId, axisId: integer): integer;
begin
  Result := 0;

  if deviceId < joystickList.Count then
  begin
    case axisId of
      0: Result := round(joystickAxisList[deviceId].y1);
      1: Result :=
          round(joystickAxisList[deviceId].y2);
    end;
  end;

end;

function TInputHandle.isButtonDown(deviceId, buttonId: integer): boolean;
begin
  Result := (deviceId < joystickButtonList.Count) or
    joystickButtonList[deviceId][buttonId];
end;

function TInputHandle.getMousePosition(): TVector2;
begin
  Result := mousePosition;
end;

function TInputHandle.isMouseButtonDown(buttonId: integer): boolean;
begin
  Result := mouseButtonList[buttonId];
end;

function TInputHandle.isKeyDown(scancode: TSDL_ScanCode): boolean;
begin
  if keyState <> nil then
    Result := keyState[scancode] > 0
  else
    Result := False;
end;


procedure TInputHandle.Update;
var
  e: TSDL_Event;
begin
  while SDL_PollEvent(@e) = 1 do
  begin
    case e.Type_ of
      SDL_QUITEV: isQuit := True;
      SDL_JOYAXISMOTION: onJoystickAxisMotionEvent(
          e.jaxis.which, e.jaxis.axis, e.jaxis.Value);
      SDL_JOYBUTTONDOWN, SDL_JOYBUTTONUP: onJoystickButtonEvent(
          e.jaxis.which, e.jbutton.button, SDL_JOYBUTTONDOWN = e.type_);
      SDL_MOUSEMOTION:
        onMouseMotionEvent(e.motion.x, e.motion.y);
      SDL_MOUSEBUTTONUP, SDL_MOUSEBUTTONDOWN: onMouseButtonEvent(
          e.button.button, SDL_MOUSEBUTTONDOWN = e.type_);
      SDL_KEYUP, SDL_KEYDOWN: onKeyEvent;
    end;
  end;

end;

end.
